﻿#ifndef ___PCLIB_S_DBPOOL___
#define ___PCLIB_S_DBPOOL___


#include "../m_core/m_core.hpp"
#include "s_db.hpp"

/**
*@brief 抽象的数据库连接池，T要求继承于CPCIDB
配置示例：
[Pool1]
#服务器地址
HostName=127.0.0.1
#服务器端口
Port=3306
#用户名
User=root
#密码
Password=
#数据库名
DBName=test
#字符集
CharacterSet=utf8mb4
#数据库连接读写超时时间,ms
Timeout=20000
#是否使用增量(如果不使用的话，启动时一次性打开所有的连接且后面的增量值可以不配置)
HasStep=true
#最大连接数
MaxConnectNumber=50
#最小连接数(启动时打开的连接数)
MinConnectNumber=2
#增加阈值（空闲连接小于阈值，触发增加）
IncreaseThreshold=5
#减少阈值（空闲连接大于阈值，触发减少）
DecreaseThreshold=15
*/
template<typename T>
class CPCDBPool : CPCNoCopyable
{
public:

    /**
    *@brief     连接池初始化
    *@param     cfgPath[in]     配置文件位置
    *@param     poolName[in]    池名称，也是配置文件section
    *@return    是否成功
    */
    CPCResult<> Init(const char* cfgPath, const char* poolName)
    {
        m_poolName = poolName ? poolName : "";

        CPCConfig cfgFile(cfgPath);
        return this->Init(cfgFile, m_poolName.c_str());
    }

    //连接池初始化
    CPCResult<> Init(CPCConfig& cfgFile, const char* poolName)
    {
        CPCResult<> result;
        m_HostName = cfgFile.GetString(poolName, "HostName", "127.0.0.1");
        m_Port = cfgFile.GetInt(poolName, "Port", 3306);
        m_User = cfgFile.GetString(poolName, "User", "root");
        m_Password = cfgFile.GetString(poolName, "Password", "");
        m_DBName = cfgFile.GetString(poolName, "DBName", "");
        m_CharacterSet = cfgFile.GetString(poolName, "CharacterSet", "utf8mb4");
        m_Timeout = cfgFile.GetInt(poolName, "Timeout", 20000);

        m_HasStep = cfgFile.GetBool(poolName, "HasStep", true);
        m_MaxConnectNumber = cfgFile.GetInt(poolName, "MaxConnectNumber", 50);
        m_MinConnectNumber = cfgFile.GetInt(poolName, "MinConnectNumber", 2);
        m_IncreaseThreshold = cfgFile.GetInt(poolName, "IncreaseThreshold", 5);
        m_DecreaseThreshold = cfgFile.GetInt(poolName, "DecreaseThreshold", 15);
        m_StepNumber = (m_IncreaseThreshold + m_DecreaseThreshold) / 2;

        //验证配置数据的合法性
        if (m_MaxConnectNumber == 0)
        {
            return result.SetFail("m_MaxConnectNumber=%u, invalid", m_MaxConnectNumber);
        }
        if (m_MinConnectNumber >= m_MaxConnectNumber)
        {
            return result.SetFail("m_MinConnectNumber(%u) >= m_MaxConnectNumber(%u), invalid", m_MinConnectNumber, m_MaxConnectNumber);
        }
        if (m_IncreaseThreshold > m_MaxConnectNumber)
        {
            return result.SetFail("m_IncreaseThreshold(%u) > m_MaxConnectNumber(%u), invalid", m_IncreaseThreshold, m_MaxConnectNumber);
        }
        if (m_DecreaseThreshold > m_MaxConnectNumber)
        {
            return result.SetFail("m_DecreaseThreshold(%u) > m_MaxConnectNumber(%u), invalid", m_DecreaseThreshold, m_MaxConnectNumber);
        }
        if (m_IncreaseThreshold >= m_DecreaseThreshold)
        {
            return result.SetFail("m_IncreaseThreshold(%u) >= m_DecreaseThreshold(%u), invalid", m_IncreaseThreshold, m_DecreaseThreshold);
        }

        m_Useed.clear();
        m_Idle.clear();
        m_UnConnect.clear();
        for (unsigned int i = 0; i < m_MaxConnectNumber; i++)
        {
            auto pDB = std::make_shared<T>();

            //无增量连接所有。有增量至少连一个。如果最小连接数不为0，则连接最小连接数个数。
            if (!m_HasStep || i == 0 || i < m_MinConnectNumber)
            {
                //连接
                auto ret = this->OpenConnect(pDB);
                if (!ret)
                {
                    return ret;
                }
            }
            else
            {
                m_UnConnect.push_back(pDB);
            }
        }
        return  result;
    }

    /**
    *@brief     连接池关闭并清理
    */
    void Cleanup()
    {
        CPCGuard<> guard(m_mutex);
        m_Useed.clear();
        m_Idle.clear();
        m_UnConnect.clear();
    }

    //维护一次数据库连接池，外部需要开一个定时器定时调用此接口以便维护，返回false代表发生了错误
    CPCResult<> RunOnce()
    {
        //printf("[%d,%d,%d]\n", m_Useed.size(), m_Idle.size(), m_UnConnect.size());
        //如果没有增量（连接失败会把连接对象放入未连接列表，所以也要处理）
        CPCResult<>  result;
        if (!m_HasStep)
        {
            while (1)
            {
                auto pDB = GetUnConnect();
                if (!pDB)
                {
                    //没有连接了，不能认为是错误
                    return result;
                }
                result = OpenConnect(pDB);
                if (!result)
                {
                    return result;
                }
            }
        }
        else
        {
            auto dwOpen = m_Idle.size() + m_Useed.size();
            //如果打开连接小于最小连接，先增加到最小连接。
            if (dwOpen < m_MinConnectNumber)
            {
                for (size_t i = 0; i < m_MinConnectNumber - dwOpen; i++)
                {
                    auto pDB = GetUnConnect();
                    if (!pDB)
                    {
                        break;
                    }
                    result = OpenConnect(pDB);
                    if (!result)
                    {
                        return result;
                    }
                }
            }

            //判断是否要增减
            auto dwIdle = m_Idle.size();
            auto dwUseed = m_Useed.size();
            if (dwIdle < m_IncreaseThreshold)
            {
                if (dwOpen >= m_MaxConnectNumber)
                {
                    return result;
                }
                for (size_t i = 0; i < m_StepNumber - (m_IncreaseThreshold - dwIdle); i++)
                {
                    auto pDB = GetUnConnect();
                    if (!pDB)
                    {
                        break;
                    }
                    result = OpenConnect(pDB);
                    if (!result)
                    {
                        return result;
                    }
                }
            }
            else if (dwIdle > m_DecreaseThreshold)
            {
                size_t dwDecrease;
                if (dwIdle + dwUseed - m_StepNumber >= m_MinConnectNumber)
                {
                    dwDecrease = dwIdle + dwUseed - m_StepNumber - m_MinConnectNumber;
                }
                else
                {
                    dwDecrease = dwIdle + dwUseed - m_MinConnectNumber;
                }
                if (dwDecrease > dwIdle - m_DecreaseThreshold)
                {
                    dwDecrease = dwIdle - m_StepNumber;
                }
                if (dwDecrease < 0)
                {
                    dwDecrease = 0;
                }

                for (size_t i = 0; i < dwDecrease; i++)
                {
                    //关闭一个连接
                    CPCGuard<> guard(m_mutex);
                    if (!m_Idle.empty())
                    {
                        auto pDB = m_Idle.front();
                        if (pDB)
                        {
                            pDB->Close();
                            m_UnConnect.push_back(pDB);
                        }
                        m_Idle.pop_front();
                    }
                }
            }
        }
        return result;
    }

    //从连接池中分配释放连接对象
    std::shared_ptr<T> PopDB(unsigned int retryCount = 2)
    {
        //总共retryCount次取不到，返回空
        retryCount++;
        for (unsigned int i = 0; i < retryCount; i++)
        {
            //获取连接对象
            std::shared_ptr<T> pDB;
            {
                CPCGuard<> guard(m_mutex);

                //先从idle列表中取
                if (!m_Idle.empty())
                {
                    pDB = m_Idle.front();
                    m_Idle.pop_front();
                    m_Useed.push_back(pDB);
                }
                //再从UnConnect列表中取
                else if (!m_UnConnect.empty())
                {
                    pDB = m_UnConnect.front();
                    m_UnConnect.pop_front();
                    m_Useed.push_back(pDB);
                }
            }
            if (pDB)
            {
                //如果没连接需重连
                if (!pDB->IsOpen())
                {
                    auto ret = pDB->Open(m_HostName.c_str(), m_Port, m_User.c_str(), m_Password.c_str(),
                        m_DBName.c_str(), m_CharacterSet.c_str(), m_Timeout, m_Timeout);
                    if (!ret)
                    {
                        ReleaseToUnConnect(pDB);
                        continue;
                    }
                }

                //测试连接，确认连接真的是好的
                auto ret = pDB->TestConnect();
                if (!ret)
                {
                    ReleaseToUnConnect(pDB);
                    continue;
                }
                return pDB;
            }
        }
        return nullptr;
    }

    //将分配的对象重新放入连接池
    void PushDB(std::shared_ptr<T> pDB)
    {
        if (pDB)
        {
            if (pDB->IsOpen())
            {
                this->ReleaseToIdle(pDB);
            }
            else
            {
                this->ReleaseToUnConnect(pDB);
            }
        }
    }
    

private:
    //从UnConnect列表中取一个连接
    std::shared_ptr<T> GetUnConnect()
    {
        std::shared_ptr<T> pDB;
        {
            CPCGuard<> guard(m_mutex);
            if (!m_UnConnect.empty())
            {
                pDB = m_UnConnect.front();
                m_UnConnect.pop_front();
                //m_Idle.push_back(pDB);	//这个时候应该放到Used列表中去，防止被GetDB又拿到了
                m_Useed.push_back(pDB);
            }
        }
        return pDB;
    }

    //打开一个连接
    CPCResult<> OpenConnect(std::shared_ptr<T> pDB)
    {
        //如果没连接需重连
        if (!pDB->IsOpen())
        {
            auto ret = pDB->Open(m_HostName.c_str(), m_Port, m_User.c_str(), m_Password.c_str(),
                m_DBName.c_str(), m_CharacterSet.c_str(), m_Timeout, m_Timeout);
            if (!ret)
            {
                ReleaseToUnConnect(pDB);
                return ret;
            }
        }

        //连接成功后将连接从Used中释放到Idle
        CPCResult<> ret;
        ReleaseToIdle(pDB);
        return ret;
    }

    //将连接转移到空闲队列
    void ReleaseToIdle(std::shared_ptr<T> pDB)
    {
        CPCGuard<> guard(m_mutex);
        if (pDB)
        {
            //必然在Useed列表中
            if (!m_Useed.empty())
            {
                m_Useed.remove(pDB);
            }
            m_Idle.push_back(pDB);
        }
    }

    //将连接关闭，并释放到未连接队列
    void ReleaseToUnConnect(std::shared_ptr<T> pDB)
    {
        CPCGuard<> guard(m_mutex);
        if (pDB)
        {
            pDB->Close();
            //必然在Useed列表中
            if (!m_Useed.empty())
            {
                m_Useed.remove(pDB);
            }
            m_UnConnect.push_back(pDB);
        }
    }

private:
    std::string m_poolName; //池名称
    
    std::string m_HostName; //服务器地址
    unsigned short m_Port;  //服务器端口
    std::string m_User;     //用户名
    std::string m_Password; //密码
    std::string m_DBName;   //数据库名
    std::string m_CharacterSet; //字符集
    unsigned int m_Timeout;     //数据库连接读写超时时间,ms

    bool         m_HasStep;             //是否使用增量(如果不使用的话，启动时一次性打开所有的连接且后面的增量值可以不配置)
    unsigned int m_MaxConnectNumber;    //最大连接数
    unsigned int m_MinConnectNumber;    //最小连接数(启动时打开的连接数)
    unsigned int m_IncreaseThreshold;   //增加阈值（空闲连接小于阈值，触发增加）
    unsigned int m_DecreaseThreshold;   //减少阈值（空闲连接大于阈值，触发减少）

    std::list <std::shared_ptr<T> > m_Useed;     //正在使用的队列（已连接）
    std::list <std::shared_ptr<T> > m_Idle;      //空闲队列（已连接）
    std::list <std::shared_ptr<T> > m_UnConnect; //未连接队列（未连接）
    
    CPCRecMutex                      m_mutex;
    unsigned int                     m_StepNumber;   //每次增加或减少的数量
};


#endif    //___PCLIB_S_DBPOOL___